// copyright (c) 2021 HUANG WEI QIANG
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "driver/uart.h"
#include "freertos/queue.h"
#include "esp_log.h"
#include "sdkconfig.h"

#define TAG "D700_MODBUS"
#define RXD (4)
#define TXD (5)
#define RTS (18)
#define CTS (UART_PIN_NO_CHANGE)
#define BAUD_RATE (9600)
#define UART_PORT (2)
//The following parameters of the inverter need to be modified to specified value.
//pr.79 = 0,2; pr.118 = 96; pr.120 = 0; pr.121 = 9999; pr.122 = 120; pr.182 = 66; pr.549 = 1;

#define BUF_SIZE        (127)

// Read packet timeout
#define PACKET_READ_TICS        (100 / portTICK_RATE_MS)

// Timeout threshold for UART = number of symbols (~10 tics) with unchanged state on receive pin
#define ECHO_READ_TOUT          (3) // 3.5T * 8 = 28 ticks, TOUT=3 -> ~24..33 ticks

#pragma pack(push,1)
typedef struct{
    uint8_t slave_addr;
    uint8_t command;
    uint16_t reg_addr;
    uint16_t nums; //preset data in d700_modbus_set_param() function
    uint16_t crc;
    }d700_query_t;
#pragma pack(pop)

uint16_t param_value[10];

static d700_query_t d700_query;
static char *crc_p = (char*)&d700_query;


const int uart_num = UART_PORT;
uart_config_t uart_config = {
        .baud_rate = BAUD_RATE,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_2,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
        .rx_flow_ctrl_thresh = 122,
        .source_clk = UART_SCLK_APB,
};

static uint16_t crc_16_modbus(char* buf,int buf_len){
    uint16_t temp = 0xffff;
    for(int i = 0; i < buf_len; i++){
        temp = buf[i] ^ temp;
        for(int j = 0; j < 8; j++){
            if(temp & 0x01){
                temp = temp >> 1;
                temp = temp ^ 0xa001;
            }else{
                temp = temp >> 1;
            }
        }
    }
    return temp;
}

static uint16_t hwq_htons(uint16_t short_num){
    uint16_t temp_0 = short_num >> 8;
    short_num = short_num << 8;
    return (short_num|temp_0);
}

void modbus_send(const int port, const char* str, uint8_t length)
{
    if (uart_write_bytes(port, str, length) != length) {
       printf("uart_writ_bytes() error!\n"); 
    }else printf("modbus_send() success\n");
}


void d700_modbus_init(void){
    // Set UART log level
    esp_log_level_set(TAG, ESP_LOG_INFO);

    ESP_LOGI(TAG, "Start RS485 application test and configure UART.");

    // Install UART driver (we don't need an event queue here)
    // In this example we don't even use a buffer for sending data.
    ESP_ERROR_CHECK(uart_driver_install(uart_num, BUF_SIZE * 2, 0, 0, NULL, 0));

    // Configure UART parameters
    ESP_ERROR_CHECK(uart_param_config(uart_num, &uart_config));

    ESP_LOGI(TAG, "UART set pins, mode and install driver.");

    // Set UART pins as per KConfig settings
    ESP_ERROR_CHECK(uart_set_pin(uart_num, TXD, RXD, RTS, CTS));

    // Set RS485 half duplex mode
    ESP_ERROR_CHECK(uart_set_mode(uart_num, UART_MODE_RS485_HALF_DUPLEX));

    // Set read timeout of UART TOUT feature
    ESP_ERROR_CHECK(uart_set_rx_timeout(uart_num, ECHO_READ_TOUT));
}

//argument nums should less than 10.
void d700_modbus_read_param(const uint8_t slave_addr, const uint16_t param_num, uint16_t * data_value, const uint16_t reg_addr,const uint16_t nums){
    d700_query.slave_addr = slave_addr;
    d700_query.command = 0x03;
    if(param_num<1000 && reg_addr ==0) {d700_query.reg_addr = 999 + param_num;}
    else {d700_query.reg_addr = reg_addr - 40001;}
    d700_query.reg_addr = hwq_htons(d700_query.reg_addr);
    d700_query.nums = hwq_htons(nums);
    d700_query.crc = crc_16_modbus(crc_p,sizeof(d700_query)-2);
    printf("the data what will be sended: 0x%02x,0x%02x,0x%04x,0x%04x,0x%04x\n",d700_query.slave_addr,d700_query.command,d700_query.reg_addr,d700_query.nums,d700_query.crc);
    uint8_t* data = (uint8_t*) malloc(BUF_SIZE);
    for(int i = 0; i < 3; i++){                 //The communication may be repeated a maximum of 3 times.
        modbus_send(uart_num,crc_p,sizeof(d700_query));
        ESP_ERROR_CHECK(uart_wait_tx_done(uart_num, 10));
        vTaskDelay(200/portTICK_PERIOD_MS);
        int len = uart_read_bytes(uart_num, data, BUF_SIZE, PACKET_READ_TICS); 
        if (len <= 0) {printf("uart_buffer is empty.\n"); continue;}
        if((uint8_t)data[0] != slave_addr) {printf("slave_addr is not right!\n"); continue;}
        if((uint8_t)data[1] != 0x03) {printf("query command was wrong!\n"); continue;}
        if((uint8_t)data[2] != nums*2) {printf("param nums wrong!\n"); continue;}
        uint16_t* crc_temp = (uint16_t *)&data[3+nums*2];
        if(*crc_temp == crc_16_modbus((char *)data,3+nums*2)) {
            printf("read from machine success\n");
            for(int j = 0; j < nums; j++){
                crc_temp = (uint16_t *)&data[3+j*2];
                param_value[j] = hwq_htons(*crc_temp);
            }
            *data_value = param_value[0];
            break;
        }else printf("crc check wrong!\n");
    }
    free(data);
}

//set only one param.
void d700_modbus_set_param(const uint8_t slave_addr, const uint16_t param_num, const uint16_t data_value, const uint16_t reg_addr){

    d700_query.slave_addr = slave_addr;
    d700_query.command = 0x06;
    if(param_num<1000 && reg_addr ==0) {d700_query.reg_addr = 999 + param_num;}
    else {d700_query.reg_addr = reg_addr - 40001;}
    d700_query.reg_addr = hwq_htons(d700_query.reg_addr);
    d700_query.nums = hwq_htons(data_value);
    d700_query.crc = crc_16_modbus(crc_p,sizeof(d700_query_t)-2);
    printf("SETTING: the data what will be sended: 0x%02x,0x%02x,0x%04x,0x%04x,0x%04x\n",d700_query.slave_addr,d700_query.command,d700_query.reg_addr,d700_query.nums,d700_query.crc);

    d700_query_t resp;
    for(int i = 0; i < 3; i++){                 //The communication may be repeated a maximum of 3 times.
        modbus_send(uart_num,crc_p,sizeof(d700_query));
        ESP_ERROR_CHECK(uart_wait_tx_done(uart_num, 10));
        vTaskDelay(100/portTICK_PERIOD_MS);
        int len = uart_read_bytes(uart_num, &resp, sizeof(resp), PACKET_READ_TICS); 
        if (len <= 0) {printf("uart_buffer is empty.\n"); continue;}
        if(memcmp(&resp, &d700_query,sizeof(d700_query)) == 0) {printf("SETTING: param set successful.\n"); break;}
        if(resp.slave_addr != slave_addr)  {printf("setted slave_addr wrong!\n"); break;}
        if(resp.command != 0x06) {printf("set command wrong!\n"); break;}
        if(resp.reg_addr != d700_query.reg_addr) {printf("set reg_addr wrong!\n"); break;}
        if (resp.nums != data_value) {printf("setted data is not right.\n"); continue;}
        if(resp.crc != d700_query.crc) {printf("SETTING: crc check wrong!\n"); continue;}
        
    }
}